/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package common

import (
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/consensus/common"
	dposcomm "hundsun.com/hsl/hschain/consensus/impl/dpos/common"
	"hundsun.com/hsl/hschain/consensus/types"
	"hundsun.com/hsl/hschain/protos"
	protoscomm "hundsun.com/hsl/hschain/protos/common"
	pbdpos "hundsun.com/hsl/hschain/protos/consensus/dpos"
)

var currentRoundMu sync.RWMutex
var currentRound int64
var currentRoundMiners map[protos.Address]*protoscomm.Miner
var currentRoundMinersIndex map[protos.Address]int

func initRoundCommon(ctx context.Context, baseclient *common.BaseClient) error {
	go func() {
		for {
			nowUnixMillis := time.Now().UnixNano() / int64(time.Millisecond)
			nextRoundStartMillis := updateCurrentRoundInfo(baseclient, nowUnixMillis)
			sleepMillis := nextRoundStartMillis - nowUnixMillis
			select {
			case <-time.After(time.Duration(sleepMillis) * time.Millisecond):
				continue
			case <-ctx.Done():
				return
			}
		}
	}()
	return nil
}

// GetCurrentRoundInfo 获取当前轮次及对应的miners
func GetCurrentRoundInfo() (int64, map[protos.Address]*protoscomm.Miner) {
	for !initialized.Load() {

	}
	currentRoundMu.RLock()
	defer currentRoundMu.RUnlock()
	return currentRound, currentRoundMiners
}

// GetCurrentRoundMinerIndex 获取轮次及制定miner在当前轮次miner的索引
func GetCurrentRoundMinerIndex(minerAddress protos.Address) (int64, int) {
	for !initialized.Load() {

	}
	currentRoundMu.RLock()
	defer currentRoundMu.RUnlock()
	index, ok := currentRoundMinersIndex[minerAddress]
	if !ok {
		return currentRound, minerIndexNotFound
	}
	return currentRound, index
}

func updateCurrentRoundInfo(baseclient *common.BaseClient, nowUnixMillis int64) int64 {
	currentRoundMu.Lock()
	defer currentRoundMu.Unlock()
	round, roundStartMillis, err := dposcomm.CalculateRound(nowUnixMillis)
	if err != nil {
		panic(err)
	}
	log.Infof("updating currentRound from '%d' to '%d'", currentRound, round)
	var roundMiners = &pbdpos.RoundMiners{}
	roundMinerBytes, err := baseclient.RequestRoundMinersInfo(round, types.DefaultMqTimeOut)
	if len(roundMinerBytes) > 0 {
		err = proto.Unmarshal(roundMinerBytes, roundMiners)
		if err != nil {
			log.Errorf("unmarshal round miner info failed, err: %s", err.Error())
			return roundStartMillis + dposcomm.EpochIntervalMillis
		}
	}
	var minerSlice []*protoscomm.Miner
	currentRound = round
	if len(roundMiners.Miners) > 0 {
		minerSlice = roundMiners.Miners
	} else {
		minerSlice = baseclient.GetCurrentMiners()
	}
	currentRoundMiners, currentRoundMinersIndex = convertMinerSliceToMap(minerSlice)
	log.Infof("currentRound updated to [%d]", round)
	if log.IsDebugEnabled() {
		rLog := log.WithField(dposcomm.LogFieldRound, currentRound)
		for i, m := range minerSlice {
			rLog.Debugf("miner[%d]:%s", i, protos.Address(m.Address).GetShortString())
		}
	}
	return roundStartMillis + dposcomm.EpochIntervalMillis
}

func convertMinerSliceToMap(miners []*protoscomm.Miner) (map[protos.Address]*protoscomm.Miner, map[protos.Address]int) {
	minerMap := make(map[protos.Address]*protoscomm.Miner, dposcomm.MaxMinersCount)
	minerIndexMap := make(map[protos.Address]int, dposcomm.MaxMinersCount)
	for i, m := range miners {
		minerMap[protos.Address(m.Address)] = m
		minerIndexMap[protos.Address(m.Address)] = i
	}
	return minerMap, minerIndexMap
}
